import importlib, sys, traceback

from functools import partial

import lib

from xdh_logging import initLog
from settings import settings

log = initLog("tags")

_data_types = {}
_data_miners = {}
_data_tags = {}
_music_types = {}

def _get_data_type(ext):
	if not ext in _data_types:
		_data_types[ext] = getattr(settings.types, ext)

	return _data_types[ext]

def _get_data_miner(ext):
	if not ext in _data_miners:
		_data_miners[ext] =  eval('lambda audio, tag: {func}'.format(func = _get_data_type(ext).data_miner))

	return _data_miners[ext]

def _get_tags(ext):
	if not ext in _data_tags:
		_data_tags[ext] = _get_data_type(ext).tags

	return _data_tags[ext]

def _get_MusicType(ext):
	if not ext in _music_types:
		_music_types[ext] = getattr(importlib.import_module(_get_data_type(ext).module), _get_data_type(ext).name)

	return _music_types[ext]

def _components(filename, test = False):
	ext = settings.re.music_ext.search(filename.lower()).group(1)

	data_type = _get_data_type(ext)

	data_miner = _get_data_miner(ext)

	tags = _get_tags(ext)

	MusicType = _get_MusicType(ext)

	filename = lib.fix_filename(filename, test)

	try:
		audio = MusicType(filename)

		yield data_miner, tags, filename, audio

	except Exception, e:
		log.error(u"Error with file: {filename}".format(filename = lib.safe_unicode(filename)))
		log.error("{name}: {msg}".format(name = e.__class__.__name__, msg = e))
		log.error("".join(traceback.format_exception(*sys.exc_info())))
		raise

def _get_normal_tag(tag, fname, test = False):
	ret = None
	for data_miner, tags, filename, audio in _components(fname, test):
		if getattr(tags, tag) in audio:
			ret = data_miner(audio, getattr(tags, tag))
	return ret

def _get_indexed_tag(tag, fname, test = False):
	ret = None
	for data_miner, tags, filename, audio in _components(fname, test):
		if getattr(tags, tag) in audio:
			pos = int(settings.re.indexed_tag.search(getattr(settings.tags, tag).type).group(1))

			value = data_miner(audio, getattr(tags, tag))
			if "/" in value:
				ret = value.split("/")[pos]
			elif value != str(value):
				ret = value[pos]
			else:
				ret = value
	return ret

def _get_split_tag(tag, fname, test = False):
	ret = []
	for data_miner, tags, filename, audio in _components(fname, test):
		if getattr(tags, tag) in audio:
			char = settings.re.split_tag.search(getattr(settings.tags, tag).type).group(1)

			values = set(getattr(settings.tags, tag).values.__dict__.values())

			ret = [item.strip() for item in data_miner(audio, getattr(tags, tag)).split(char) if item in values]

	return ret

def _get_notatag(tag, fname, test = False):
	return NotImplemented

def get_length(fname, test = False):
	for data_miner, tags, filename, audio in _components(fname, test):
		timeouts = 0

		while True:
			try:
				length = audio.info.length
				break
			except:
				if timeouts > 5:
					raise
				timeouts += 1
				time.sleep(settings.data.sleep)

	return length

def get_mod(filename, test = False):
	timeouts = 0

	while True:
		try:
			mod = datetime.fromtimestamp(os.path.getmtime(lib.fix_filename(filename, test = test)))
			return mod
		except:
			if timeouts > 5:
				raise
			timeouts += 1
			time.sleep(settings.data.sleep)

def get_tag(tag, fname, test = False):
	tag_type = getattr(settings.tags, tag).type
	timeouts = 0
	func = _get_notatag

	while True:
		try:
			if tag_type == "Normal":
				func = _get_normal_tag
                        
			elif settings.re.indexed_tag.search(tag_type) != None:
				func = _get_indexed_tag
                        
			elif settings.re.split_tag.search(tag_type) != None:
				func = _get_split_tag
        
			break
		except:
			if timeouts > 5:
				raise
			timeouts += 1
			time.sleep(settings.data.sleep)

	return func(tag, fname, test)

def _track_get_tag(tag, self):
	if not hasattr(self, "_tag_data_{tag}".format(tag = tag)):
		setattr(self, "_tag_data_{tag}".format(tag = tag), get_tag(tag, self.filename, self._test))

	return getattr(self, "_tag_data_{tag}".format(tag = tag))

def _track_get_length(self):
	if not hasattr(self, "_tag_data_length"):
		self._tag_data_length = get_length(self.filename, self._test)

	return self._tag_data_length

def _track_get_mod(self):
	if not hasattr(self, "_tag_data_modified"):
		self._tag_data_modified = get_mod(self.filename, self._test)

	return self._tag_data_modified
        
def tagged_file(excludes = None):
	"""
	Decorator to make a specific tagging mixin for tracks that sets up the tags for a given file type.
	"""
	
#"category", "flags", "length"
	def class_modifier(cls):
		all_tags = [tag for tag in settings.tags.__dict__.iterkeys() if (excludes != None and tag not in excludes) or excludes == None]
                
		if "length" not in excludes:
			setattr(cls, "length", property(fget = _track_get_length, doc = "Returns the length (in seconds) of the tracks."))

		if "modified" not in excludes:
			setattr(cls, "modified", property(fget = _track_get_mod, doc = "Returns the file modified date/time of the tracks."))

		for tag in all_tags:
			docstr = "Returns the {tag} tag's value.".format(tag = tag)
			setattr(cls, tag, property(fget = partial(_track_get_tag, tag), doc = docstr))
        
		return cls

	return class_modifier

